package com.sinan.core.connector;

import cn.hutool.core.lang.JarClassLoader;
import com.sinan.core.connector.components.PropertyContext;
import com.sinan.core.connector.components.PropertyDescriptor;
import com.sinan.core.connector.components.PropertyValue;
import com.sinan.core.connector.exception.ConnectorException;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;

/**
 * @author pengpei
 */
@Slf4j
public abstract class JdbcConnector implements Connector {

    private final PropertyContext propertyContext;

    private Connection connection;

    private JarClassLoader driverClassLoader;

    private boolean closed = false;

    private String productVersion;

    public static final PropertyDescriptor USER = PropertyDescriptor.builder()
            .name("user")
            .displayName("User")
            .description("连接数据库的账号")
            .defaultValue("")
            .build();

    public static final PropertyDescriptor PASSWORD = PropertyDescriptor.builder()
            .name("password")
            .displayName("Password")
            .description("连接数据库的密码")
            .defaultValue("")
            .build();

    public JdbcConnector(PropertyContext propertyContext) {
        this.propertyContext = propertyContext;
        validateProperties();
    }

    @Override
    public void setProductVersion(String productVersion) {
        this.productVersion = productVersion;
    }

    @Override
    public String getProductVersion() {
        if (productVersion == null || productVersion.isEmpty() || productVersion.isBlank()) {
            throw new NullPointerException("product version is null or empty");
        }
        return this.productVersion;
    }

    @Override
    public PropertyValue getProperty(PropertyDescriptor descriptor) {
        return propertyContext.getProperty(descriptor);
    }

    @Override
    public void open(int timeout) {
        CompletableFuture<Void> future = CompletableFuture.runAsync(this::open);
        try {
            future.get(timeout, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            throw new ConnectorException(e);
        }
    }

    @Override
    public List<PropertyDescriptor> getSupportedPropertyDescriptors() {
        return Stream.of(USER, PASSWORD).toList();
    }

    protected abstract String getDriverClass();

    protected abstract String getUrl();

    @Override
    public void open() {
        log.debug("open connector");
        if (driverClassLoader == null) {
            driverClassLoader = JarClassLoader.load(getDriverPath());
        }
        try {
            Class<? extends Driver> clazz = (Class<Driver>) driverClassLoader.loadClass(getDriverClass());
            Driver driver = clazz.getConstructor().newInstance();

            Properties properties = new Properties();
            properties.setProperty(USER.getName(), propertyContext.getProperty(USER).getValue());
            properties.setProperty(PASSWORD.getName(), propertyContext.getProperty(PASSWORD).getValue());

            connection = driver.connect(getUrl(), properties);
            closed = false;
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
                 InvocationTargetException | SQLException e) {
            throw new ConnectorException(e);
        }
    }

    public Connection getConnection() {
        if (!closed) {
            open();
        }
        return connection;
    }

    public Connection getConnection(int timeout) {
        if (!closed) {
            open(timeout);
        }
        return connection;
    }

    @Override
    public void close() throws IOException {
        log.debug("close connector");
        if (connection != null) {
            try {
                connection.close();
                connection = null;
            } catch (SQLException e) {
                throw new ConnectorException(e);
            }
        }
        if (driverClassLoader != null) {
            driverClassLoader.close();
            driverClassLoader = null;
        }
        closed = true;
    }

    @Override
    public boolean isClosed() {
        return false;
    }

    abstract File getDriverPath();

}
